Skip to content

Close the streaming HTTP body on the Anthropic and OpenAI-Responses streaming paths - #718

Merged
qmuntal merged 1 commit into
microsoft:mainfrom
PratikDhanaveFork:close-streaming-http-body
Jul 24, 2026
Merged

Close the streaming HTTP body on the Anthropic and OpenAI-Responses streaming paths#718
qmuntal merged 1 commit into
microsoft:mainfrom
PratikDhanaveFork:close-streaming-http-body

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

The Anthropic Messages streaming path (anthropicprovider/agent.go) and the OpenAI Responses streaming paths (openaiprovider/responses.go) create an SSE stream via NewStreaming / GetStreaming and iterate it with for stream.Next(), but never call Close() on the stream. On early consumer termination (yield returns false when the caller breaks out of the range) or context cancellation, the closure returns without releasing the HTTP response body.

The SDK's Stream.Close() releases the body (Stream.Close -> decoder.Close -> ReadCloser.Close). Without it, the body is never returned to the connection pool and the underlying connection leaks.

This adds defer func() { _ = stream.Close() }() immediately after each streaming call:

  • anthropicprovider/agent.go: after Messages.NewStreaming
  • openaiprovider/responses.go: after Responses.GetStreaming (continuation-token resume) and after Responses.NewStreaming

Why

The sibling Chat Completions path (openaiprovider/chat.go) already does exactly this — stream := ...NewStreaming(...) immediately followed by defer func() { _ = stream.Close() }(). This change brings the Anthropic and Responses paths in line with it. It also matches the .NET and Python SDKs, which dispose the streaming response when enumeration ends early, so streaming lifecycle semantics are consistent across the SDK ports.

Testing

Added a black-box test to each package's canonical test file (agent_test.go, responses_test.go) that:

  • serves a valid SSE stream from an httptest.Server,
  • injects an http.Client whose RoundTripper wraps the response body in a Close-counting ReadCloser,
  • drives the provider's streaming Run and breaks out of the range after the first update,
  • asserts the body's Close() was invoked.

Each test fails before the fix (Close count 0) and passes after (count 1). go build ./..., go vet, and go test pass for both changed packages.

Copilot AI review requested due to automatic review settings July 24, 2026 03:00
@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 24, 2026 03:00
@PratikDhanave
PratikDhanave force-pushed the close-streaming-http-body branch from 9b1f145 to e188434 Compare July 24, 2026 03:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes a streaming resource-leak issue by ensuring SSE streams are explicitly closed in the Anthropic Messages and OpenAI Responses streaming implementations, aligning these paths with the existing OpenAI Chat Completions behavior and preventing HTTP connection leaks on early consumer termination or context cancellation.

Changes:

  • Added defer ...Close() immediately after creating streaming SSE streams in anthropicprovider/agent.go and both streaming branches of openaiprovider/responses.go.
  • Added black-box tests in each provider package to verify the underlying HTTP response body is closed when the consumer stops iteration early.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
provider/openaiprovider/responses.go Ensures OpenAI Responses streaming streams are closed on all exit paths (including early consumer exit).
provider/openaiprovider/responses_test.go Adds a transport/body wrapper test to assert the streaming response body is closed when iteration ends early.
provider/anthropicprovider/agent.go Ensures Anthropic Messages streaming stream is closed via defer to avoid leaking the HTTP body.
provider/anthropicprovider/agent_test.go Adds a transport/body wrapper test to assert the streaming response body is closed when iteration ends early.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 24, 2026
@qmuntal
qmuntal enabled auto-merge July 24, 2026 09:32
@qmuntal
qmuntal added this pull request to the merge queue Jul 24, 2026
…aths

The Anthropic Messages and OpenAI Responses streaming paths iterated the
SSE stream but never called Close(), so on early consumer termination
(yield returns false) or context cancellation the HTTP response body was
never returned to the connection pool, leaking the underlying connection.

Add a deferred stream.Close() immediately after each NewStreaming and
GetStreaming call, mirroring the Chat Completions path which already does
this and matching the .NET/Python SDKs that dispose the streaming
response on early enumeration.
@PratikDhanave
PratikDhanave force-pushed the close-streaming-http-body branch from e188434 to dd687df Compare July 24, 2026 09:33
Merged via the queue into microsoft:main with commit 5b27c97 Jul 24, 2026
2 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review: No Issues Found

This PR fixes a streaming HTTP body leak by adding defer stream.Close() on the Anthropic and OpenAI-Responses streaming paths, aligning them with the existing OpenAI Chat Completions path.

Scope: Internal implementation fix only — no exported Go APIs were added, removed, or changed.

Cross-repo parity: This is a convergence fix that matches .NET and Python SDK behavior (both dispose/close streaming responses on early enumeration exit). No parity issues.

Labels: parity-approved is already present and correct. No public-api-change label warranted.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 17.8 AIC · ⌖ 4.87 AIC · ⊞ 5.9K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parity-approved Go API consistency review found no parity issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants